﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Interface;
using MugenInjection.Interception.Proxy;

// ReSharper disable CheckNamespace

namespace MugenInjection.Interception
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Represents the default interceptor.
    /// </summary>
    internal class DefaultInterceptor : IEventAddInterceptor, IEventRemoveInterceptor, IMethodInterceptor,
                                        IPropertyGetInterceptor, IPropertySetInterceptor
    {
        #region Nested type: InterceptorTypeCache

        private sealed class InterceptorTypeCache
        {
            #region Fields

            public readonly InterceptorType Type;
            public readonly MemberInfo Member;

            #endregion

            #region Constructors

            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public InterceptorTypeCache(MemberInfo member, InterceptorType type)
            {
                Member = member;
                Type = type;
            }

            #endregion

        }

        #endregion

        #region Fields

#if !NETFX_CORE
        /// <summary>
        /// Default search flags.
        /// </summary>
        private const BindingFlags SearchFlag =
            BindingFlags.NonPublic | BindingFlags.Instance |
            BindingFlags.Public | BindingFlags.InvokeMethod |
            BindingFlags.SetProperty | BindingFlags.GetProperty
            | BindingFlags.IgnoreReturn;
#endif

        private static readonly Dictionary<MemberInfo, InterceptorTypeCache> InterceptorTypes =
            new Dictionary<MemberInfo, InterceptorTypeCache>();
        private readonly MethodProxy _methodProxy;
        private readonly MethodInfo _methodInfo;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultInterceptor"/> class.
        /// </summary>
        public DefaultInterceptor(MethodProxy methodProxy)
        {
            Validate.ArgumentNotNull(methodProxy, "methodProxy");
            _methodProxy = methodProxy;
            _methodInfo = (MethodInfo)_methodProxy.Member;
            var interceptorType = GetInterceptorType(_methodInfo);
            InterceptorType = interceptorType.Type;
            Member = interceptorType.Member;
        }

        #endregion

        #region Methods

        private static InterceptorTypeCache GetInterceptorType(MethodInfo methodInfo)
        {
            InterceptorTypeCache interceptorType;
            lock (InterceptorTypes)
            {
                if (InterceptorTypes.TryGetValue(methodInfo, out interceptorType))
                    return interceptorType;
                if (methodInfo.IsSpecialName)
                {
                    string name = methodInfo.Name;
                    bool isGetter = name.StartsWith("get_", StringComparison.Ordinal);
                    bool isSetter = name.StartsWith("set_", StringComparison.Ordinal);
                    bool isAddEvent = name.StartsWith("add_", StringComparison.Ordinal);
                    bool isRemoveEvent = name.StartsWith("remove_", StringComparison.Ordinal);
                    if (isGetter || isSetter)
                    {
                        string propertyName = name.Substring(4);
                        PropertyInfo propertyInfo = null;
                        if (propertyName == "Item")
                        {
                            var parameters = methodInfo.GetParameters();
                            foreach (var property in methodInfo.DeclaringType.GetProperties())
                            {
                                if (property.Name != "Item") continue;
                                MethodInfo propertyMethod;
                                if (isSetter)
                                    propertyMethod = property.GetSetMethod(true);
                                else
                                    propertyMethod = property.GetGetMethod(true);


                                if (propertyMethod == null || propertyMethod.ReturnType != methodInfo.ReturnType)
                                    continue;
                                var propertyParameters = propertyMethod.GetParameters();
                                if (propertyParameters.Length != parameters.Length) continue;
                                bool find = true;
                                for (int i = 0; i < propertyParameters.Length; i++)
                                {
                                    if (propertyParameters[i].ParameterType != parameters[i].ParameterType)
                                    {
                                        find = false;
                                        break;
                                    }
                                }
                                if (!find) continue;
                                propertyInfo = property;
                                break;
                            }
                        }
                        else
#if NETFX_CORE
                            propertyInfo = methodInfo.DeclaringType.GetProperty(propertyName);
#else
                            propertyInfo = methodInfo.DeclaringType.GetProperty(propertyName, SearchFlag);
#endif
                        if (propertyInfo != null)
                        {
                            interceptorType = new InterceptorTypeCache(propertyInfo,
                                                                       isGetter
                                                                           ? InterceptorType.PropertyGet
                                                                           : InterceptorType.PropertySet);
                        }
                    }
                    if (isAddEvent || isRemoveEvent)
                    {
                        string eventName = name.Substring(isAddEvent ? 4 : 7);
#if NETFX_CORE
                        EventInfo eventInfo = methodInfo.DeclaringType.GetEvent(eventName);
#else
                        EventInfo eventInfo = methodInfo.DeclaringType.GetEvent(eventName, SearchFlag);
#endif
                        if (eventInfo != null)
                        {
                            interceptorType = new InterceptorTypeCache(eventInfo,
                                                                       isAddEvent
                                                                           ? InterceptorType.EventAdd
                                                                           : InterceptorType.EventRemove);
                        }
                    }
                    if (interceptorType == null)
                        interceptorType = new InterceptorTypeCache(null, InterceptorType.Unknown);
                }
                else
                {
                    interceptorType = new InterceptorTypeCache(methodInfo, InterceptorType.Method);
                }
                InterceptorTypes[methodInfo] = interceptorType;
            }
            return interceptorType;
        }

        #endregion

        #region Implementation of IInterceptor

        /// <summary>
        /// Gets a proxy object for the <see cref="IInterceptor"/>.
        /// </summary>
        public IProxy Proxy
        {
            get { return _methodProxy; }
        }

        /// <summary>
        /// The target of the interception.
        /// </summary>
        public object Target { get; set; }

        /// <summary>
        /// Gest the type of current <see cref="IInterceptor"/>.
        /// </summary>
        public InterceptorType InterceptorType { get; private set; }

        /// <summary>
        /// Gets the <see cref="MemberInfo"/> for the intercept. 
        /// </summary>
        public MemberInfo Member { get; private set; }

        /// <summary>
        /// Gets the chain of interceptors that will be executed before the target method is called.
        /// </summary>
        public IList<IInterceptorProcess> Interceptors { get; internal set; }

        /// <summary>
        /// Invokes the original method of the member.
        /// </summary>
        public void ProcessOriginal()
        {
            if (IsAbstract)
                throw new InvalidOperationException("Cannot call an abstract method.");
            _methodProxy.Method();
        }

        /// <summary>
        /// Invokes the method in a target.
        /// </summary>
        public void ProcessInTarget()
        {
            var target = Target;
            if (target == null)
                throw new NullReferenceException("The Target property is null.");
            _methodProxy.ReturnValue = InjectorUtils
                 .ReflectionAccessProvider
                 .GetInvokeMethodDelegate<object>(_methodInfo)
                 .Invoke(Target, _methodProxy.InputParameters);
        }

        /// <summary>
        /// Indicates that no member of the base implementation.
        /// </summary>
        public bool IsAbstract
        {
            get { return _methodProxy == null || _methodProxy.Method == null; }
        }

        #endregion

        #region Implementation of IEventAddInterceptor

        /// <summary>
        /// Gets the original method of adding events.
        /// </summary>
        public Action AddMethod
        {
            get
            {
                if (InterceptorType != InterceptorType.EventAdd)
                    throw new NotSupportedException();
                return _methodProxy.Method;
            }
        }

        /// <summary>
        /// Gets or sets the event to be added.
        /// </summary>
        public Delegate AddValue
        {
            get
            {
                if (InterceptorType != InterceptorType.EventAdd)
                    throw new NotSupportedException();
                if (_methodProxy.InputParameters == null)
                    return null;
                return (Delegate)_methodProxy.InputParameters[0];
            }
            set
            {
                if (InterceptorType != InterceptorType.EventAdd)
                    throw new NotSupportedException();
                _methodProxy.InputParameters[0] = value;
            }
        }

        #endregion

        #region Implementation of IEventRemoveInterceptor

        /// <summary>
        /// Gets the original method of removing events.
        /// </summary>
        public Action RemoveMethod
        {
            get
            {
                if (InterceptorType != InterceptorType.EventRemove)
                    throw new NotSupportedException();
                return _methodProxy.Method;
            }
        }

        /// <summary>
        /// Gets or sets the event to be removed.
        /// </summary>
        public Delegate RemoveValue
        {
            get
            {
                if (InterceptorType != InterceptorType.EventRemove)
                    throw new NotSupportedException();
                if (_methodProxy.InputParameters == null)
                    return null;
                return (Delegate)_methodProxy.InputParameters[0];
            }
            set
            {
                if (InterceptorType != InterceptorType.EventRemove)
                    throw new NotSupportedException();
                _methodProxy.InputParameters[0] = value;
            }
        }

        #endregion

        #region Implementation of IMethodInterceptor

        /// <summary>
        /// Gets the input parameters of the method.
        /// </summary>
        public object[] InputParameters
        {
            get
            {
                if (InterceptorType != InterceptorType.Method)
                    throw new NotSupportedException();
                return _methodProxy.InputParameters;
            }
        }

        /// <summary>
        /// Gets or sets the output parameter of the method.
        /// </summary>
        public object ReturnValue
        {
            get
            {
                if (InterceptorType != InterceptorType.Method)
                    throw new NotSupportedException();
                return _methodProxy.ReturnValue;
            }
            set
            {
                if (InterceptorType != InterceptorType.Method)
                    throw new NotSupportedException();
                _methodProxy.ReturnValue = value;
            }
        }

        /// <summary>
        /// Gets the original method.
        /// </summary>
        public Action Method
        {
            get
            {
                if (InterceptorType != InterceptorType.Method)
                    throw new NotSupportedException();
                return _methodProxy.Method;
            }
        }

        #endregion

        #region Implementation of IPropertyGetInterceptor

        /// <summary>
        /// Gets the original method of the get property.
        /// </summary>
        public Action GetMethod
        {
            get
            {
                if (InterceptorType != InterceptorType.PropertyGet)
                    throw new NotSupportedException();
                return _methodProxy.Method;
            }
        }

        /// <summary>
        /// Gets or sets the property value.
        /// </summary>
        public object GetValue
        {
            get
            {
                if (InterceptorType != InterceptorType.PropertyGet)
                    throw new NotSupportedException();
                if (_methodProxy.InputParameters == null)
                    return null;
                return _methodProxy.ReturnValue;
            }
            set
            {
                if (InterceptorType != InterceptorType.PropertyGet)
                    throw new NotSupportedException();
                _methodProxy.ReturnValue = value;
            }
        }

        #endregion

        #region Implementation of IPropertySetInterceptor

        /// <summary>
        /// Gets the original method of the set property.
        /// </summary>
        public Action SetMethod
        {
            get
            {
                if (InterceptorType != InterceptorType.PropertySet)
                    throw new NotSupportedException();
                return _methodProxy.Method;
            }
        }

        /// <summary>
        /// Gets or sets the property value.
        /// </summary>
        public object SetValue
        {
            get
            {
                if (InterceptorType != InterceptorType.PropertySet)
                    throw new NotSupportedException();
                if (_methodProxy.InputParameters == null)
                    return null;
                return _methodProxy.InputParameters[0];
            }
            set
            {
                if (InterceptorType != InterceptorType.PropertySet)
                    throw new NotSupportedException();
                _methodProxy.InputParameters[0] = value;
            }
        }

        #endregion
    }
}